package mt.server;

import java.util.ArrayList;
import java.util.HashMap;
import mt.Order;
import mt.comm.ServerComm;
import mt.comm.ServerSideMessage;
import mt.comm.ServerSideMessage.Type;


public class Server implements MicroTraderServer{

	/**
	 * Orders by ID Maps
	 */
	private final HashMap <Integer, Order> buyOrders, sellOrders;
	/**
	 * ID marked for future removal
	 */
	private final ArrayList <Integer> scheduledRemovals;
	/**
	 * Controls Server operation
	 */
	private static boolean STOP_SIGNAL;
	/**
	 * Orders ID
	 */
	private int orderID;
	/**
	 * Number of successful transactions
	 */
	private int transactionsNum;
	/**
	 * Number of connected clients
	 */
	private int activeConnections;
	/**
	 * Server startup timestamp
	 */
	private long startTime;

	/**
	 * Server constructor: Var initialization
	 */
	public Server(){
		System.out.println("construiu o server");
		scheduledRemovals = new ArrayList <Integer>();
		buyOrders = new HashMap<Integer, Order>();
		sellOrders = new HashMap<Integer, Order>();
		activeConnections = 0;
		transactionsNum = 0;
		orderID = 1;
		STOP_SIGNAL = false;
	}

	/**
	 * Server engine Thread: Order processing and connection status
	 * @param ServerComm Server communication module
	 */
	@Override
	public void start(ServerComm serverComm) {
		startTime = System.currentTimeMillis();
		final ServerComm srvComm = serverComm;
		serverComm.start();
		new Thread(new Runnable() {

			@Override
			public void run() {
				try {
					while(!STOP_SIGNAL){
							//System.out.println("server esta no run");
						if(srvComm.hasNextMessage()){
							ServerSideMessage msg = srvComm.getNextMessage();

							if(msg.getType().equals(Type.NEW_ORDER)){

								msg.getOrder().setServerOrderID(orderID);
								orderID++;

								if(msg.getOrder().isBuyOrder()){
									buyOrders.put(msg.getOrder().getServerOrderID(), msg.getOrder());
									System.out.println("ordem de compra" + msg.getSenderNickname()+ " " + msg.getOrder().getNumberOfUnits() + " " +
											msg.getOrder().getStock() + " " + msg.getOrder().getPricePerUnit()	);
								}
								else if(msg.getOrder().isSellOrder()){
									System.out.println("ordem de venda");
									sellOrders.put(msg.getOrder().getServerOrderID(), msg.getOrder());
								}
								processOrders(srvComm);
								Thread.sleep(500);
							}
							else if(msg.getType().equals((Type.CONNECTED))){
								for (Order buyOrder : buyOrders.values())
									srvComm.sendOrder(msg.getSenderNickname(), buyOrder);
								System.out.println("server recebeu cliente");
								for (Order sellOrder : sellOrders.values())
									srvComm.sendOrder(msg.getSenderNickname(), sellOrder);

								activeConnections++;
							}
							else if(msg.getType().equals((Type.DISCONNECTED))){
								for (Order buyOrder : buyOrders.values())
									if(buyOrder.getNickname().equals(msg.getSenderNickname()))
										scheduledRemovals.add(buyOrder.getServerOrderID());

								for (Order sellOrder : sellOrders.values())
									if(sellOrder.getNickname().equals(msg.getSenderNickname()))
										scheduledRemovals.add(sellOrder.getServerOrderID());

								activeConnections--;
								updateEntries();
							}
							else{
								srvComm.sendError(msg.getSenderNickname(), "Invalid msg.");
								System.err.println("Received an invalid message from " + msg.getSenderNickname() + ". Skipping...");
							}
						}
					}
				} catch (InterruptedException e) {
					System.err.println("Server has stopped unexpectedly!");
				}
			}
		}).start();
	}

	/**
	 * Process client orders
	 */
	private void processOrders(ServerComm srvComm){

		for (Order sellOrder : sellOrders.values()) {
			for(Order buyOrder : buyOrders.values()){

				if(buyOrder.getStock().equals(sellOrder.getStock())){

					if(sellOrder.getNumberOfUnits() > 0 && buyOrder.getNumberOfUnits() > 0){

						if(buyOrder.getPricePerUnit() == sellOrder.getPricePerUnit()){

							if(sellOrder.getNumberOfUnits() - buyOrder.getNumberOfUnits() < 0){
								buyOrder.setNumberOfUnits(sellOrder.getNumberOfUnits());
								sellOrder.setNumberOfUnits(0);
								scheduledRemovals.add(sellOrder.getServerOrderID());
							}
							else if(sellOrder.getNumberOfUnits() - buyOrder.getNumberOfUnits() > 0){
								sellOrder.setNumberOfUnits(buyOrder.getNumberOfUnits());
								buyOrder.setNumberOfUnits(0);
								scheduledRemovals.add(buyOrder.getServerOrderID());
							}
							else{
								buyOrder.setNumberOfUnits(0);
								sellOrder.setNumberOfUnits(0);
								scheduledRemovals.add(buyOrder.getServerOrderID());
								scheduledRemovals.add(sellOrder.getServerOrderID());
							}
							updateClients(srvComm, buyOrder, sellOrder);
						}
					}
				}
			}
		}
		updateEntries();
	}

	/**
	 * Performs fullfilled orders cleanup
	 */
	private void updateEntries(){
		for (Integer orderID : scheduledRemovals) {
			if(buyOrders.containsKey(orderID)){
				buyOrders.remove(orderID);
				continue;
			}
			if(sellOrders.containsKey(orderID)){
				sellOrders.remove(orderID);
			}
		}
		scheduledRemovals.clear();
	}

	/**
	 * Updates clients with successful transactions
	 */
	private void updateClients(final ServerComm srvComm, Order buyOrder, Order sellOrder){
		if(srvComm.clientIsConnected(buyOrder.getNickname()) && srvComm.clientIsConnected(sellOrder.getNickname())){
			srvComm.sendOrder(sellOrder.getNickname(), sellOrder);
			srvComm.sendOrder(buyOrder.getNickname(), buyOrder);
			transactionsNum++;
		}
	}

	/**
	 * Halt server
	 */
	public void haltServer(){
		STOP_SIGNAL = true;
	}

	/**
	 * Returns Server Uptime
	 * @return long Uptime [ms]
	 */
	public long getUpTime(){
		return System.currentTimeMillis() - startTime;
	}

	/**
	 * Returns Number of connected clients
	 * @return int Active connections
	 */
	public int getActiveConnectionsNum(){
		return activeConnections;
	}

	/**
	 * Returns number of successful transactions
	 * @return int Processed orders
	 */
	public int getTransactionsNum(){
		return transactionsNum;
	}

}